<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
  <meta content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" name="viewport"/>
  <meta content="zh-cn" http-equiv="content-language"/>
  <meta content="08 答疑现场：Spring Core 篇思考题合集" name="description"/>
  <link href="/static/favicon.png" rel="icon"/>
  <title>
   08 答疑现场：Spring Core 篇思考题合集
  </title>
  <link href="/static/index.css" rel="stylesheet"/>
  <link href="/static/highlight.min.css" rel="stylesheet"/>
  <script src="/static/highlight.min.js">
  </script>
  <meta content="Hexo 4.2.0" name="generator"/>
  <script async="" data-website-id="83e5d5db-9d06-40e3-b780-cbae722fdf8c" defer="" src="https://analyze.lianglianglee.com/umami.js">
  </script>
 </head>
 <body>
  <div class="book-container">
   <div class="book-sidebar">
    <div class="book-brand">
     <a href="/">
      <img src="/static/favicon.png"/>
      <span>
       技术文章摘抄
      </span>
     </a>
    </div>
    <div class="book-menu uncollapsible">
     <ul class="uncollapsible">
      <li>
       <a class="current-tab" href="/">
        首页
       </a>
      </li>
      <li>
       <a href="../">
        上一级
       </a>
      </li>
     </ul>
     <ul class="uncollapsible">
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3Spring%e5%9f%ba%e7%a1%80%e7%9f%a5%e8%af%86.html" id="00 导读 5分钟轻松了解Spring基础知识.md">
        00 导读 5分钟轻松了解Spring基础知识.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/00%20%e5%bc%80%e7%af%87%e8%af%8d%20%e8%b4%b4%e5%bf%83%e2%80%9c%e4%bf%9d%e5%a7%86%e2%80%9dSpring%e7%bd%a2%e5%b7%a5%e4%ba%86%e6%80%8e%e4%b9%88%e5%8a%9e%ef%bc%9f.html" id="00 开篇词 贴心“保姆”Spring罢工了怎么办？.md">
        00 开篇词 贴心“保姆”Spring罢工了怎么办？.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/01%20Spring%20Bean%20%e5%ae%9a%e4%b9%89%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="01 Spring Bean 定义常见错误.md">
        01 Spring Bean 定义常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/02%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="02 Spring Bean 依赖注入常见错误（上）.md">
        02 Spring Bean 依赖注入常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/03%20Spring%20Bean%20%e4%be%9d%e8%b5%96%e6%b3%a8%e5%85%a5%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="03 Spring Bean 依赖注入常见错误（下）.md">
        03 Spring Bean 依赖注入常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/04%20Spring%20Bean%20%e7%94%9f%e5%91%bd%e5%91%a8%e6%9c%9f%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="04 Spring Bean 生命周期常见错误.md">
        04 Spring Bean 生命周期常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/05%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="05 Spring AOP 常见错误（上）.md">
        05 Spring AOP 常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/06%20Spring%20AOP%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="06 Spring AOP 常见错误（下）.md">
        06 Spring AOP 常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/07%20Spring%e4%ba%8b%e4%bb%b6%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="07 Spring事件常见错误.md">
        07 Spring事件常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/08%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Core%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="08 答疑现场：Spring Core 篇思考题合集.md">
        08 答疑现场：Spring Core 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/09%20Spring%20Web%20URL%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="09 Spring Web URL 解析常见错误.md">
        09 Spring Web URL 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/10%20Spring%20Web%20Header%20%e8%a7%a3%e6%9e%90%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="10 Spring Web Header 解析常见错误.md">
        10 Spring Web Header 解析常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/11%20Spring%20Web%20Body%20%e8%bd%ac%e5%8c%96%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="11 Spring Web Body 转化常见错误.md">
        11 Spring Web Body 转化常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/12%20Spring%20Web%20%e5%8f%82%e6%95%b0%e9%aa%8c%e8%af%81%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="12 Spring Web 参数验证常见错误.md">
        12 Spring Web 参数验证常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/13%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="13 Spring Web 过滤器使用常见错误（上）.md">
        13 Spring Web 过滤器使用常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/14%20Spring%20Web%20%e8%bf%87%e6%bb%a4%e5%99%a8%e4%bd%bf%e7%94%a8%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="14 Spring Web 过滤器使用常见错误（下）.md">
        14 Spring Web 过滤器使用常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/15%20Spring%20Security%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="15 Spring Security 常见错误.md">
        15 Spring Security 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/16%20Spring%20Exception%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="16 Spring Exception 常见错误.md">
        16 Spring Exception 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/17%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20Web%20%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="17 答疑现场：Spring Web 篇思考题合集.md">
        17 答疑现场：Spring Web 篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/18%20Spring%20Data%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="18 Spring Data 常见错误.md">
        18 Spring Data 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/19%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8a%ef%bc%89.html" id="19 Spring 事务常见错误（上）.md">
        19 Spring 事务常见错误（上）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/20%20Spring%20%e4%ba%8b%e5%8a%a1%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af%ef%bc%88%e4%b8%8b%ef%bc%89.html" id="20 Spring 事务常见错误（下）.md">
        20 Spring 事务常见错误（下）.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/21%20Spring%20Rest%20Template%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="21 Spring Rest Template 常见错误.md">
        21 Spring Rest Template 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/22%20Spring%20Test%20%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af.html" id="22 Spring Test 常见错误.md">
        22 Spring Test 常见错误.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/23%20%e7%ad%94%e7%96%91%e7%8e%b0%e5%9c%ba%ef%bc%9aSpring%20%e8%a1%a5%e5%85%85%e7%af%87%e6%80%9d%e8%80%83%e9%a2%98%e5%90%88%e9%9b%86.html" id="23 答疑现场：Spring 补充篇思考题合集.md">
        23 答疑现场：Spring 补充篇思考题合集.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e5%af%bc%e8%af%bb%205%e5%88%86%e9%92%9f%e8%bd%bb%e6%9d%be%e4%ba%86%e8%a7%a3%e4%b8%80%e4%b8%aaHTTP%e8%af%b7%e6%b1%82%e7%9a%84%e5%a4%84%e7%90%86%e8%bf%87%e7%a8%8b.html" id="导读 5分钟轻松了解一个HTTP请求的处理过程.md">
        导读 5分钟轻松了解一个HTTP请求的处理过程.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%9f%a5%e8%af%86%e5%9b%9e%e9%a1%be%20%e7%b3%bb%e7%bb%9f%e6%a2%b3%e7%90%86Spring%e7%bc%96%e7%a8%8b%e9%94%99%e8%af%af%e6%a0%b9%e6%ba%90.html" id="知识回顾 系统梳理Spring编程错误根源.md">
        知识回顾 系统梳理Spring编程错误根源.md
       </a>
      </li>
      <li>
       <a class="menu-item" href="/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/%e7%bb%93%e6%9d%9f%e8%af%ad%20%e9%97%ae%e9%a2%98%e6%80%bb%e6%af%94%e8%a7%a3%e5%86%b3%e5%8a%9e%e6%b3%95%e5%a4%9a.html" id="结束语 问题总比解决办法多.md">
        结束语 问题总比解决办法多.md
       </a>
      </li>
      <li>
       <a href="/assets/捐赠.md">
        捐赠
       </a>
      </li>
     </ul>
    </div>
   </div>
   <div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseleave="remove_inner()" onmouseover="add_inner()">
    <div class="sidebar-toggle-inner">
    </div>
   </div>
   <div class="off-canvas-content">
    <div class="columns">
     <div class="column col-12 col-lg-12">
      <div class="book-navbar">
       <header class="navbar">
        <section class="navbar-section">
         <a onclick="open_sidebar()">
          <i class="icon icon-menu">
          </i>
         </a>
        </section>
       </header>
      </div>
      <div class="book-content" style="max-width: 960px; margin: 0 auto;
    overflow-x: auto;
    overflow-y: hidden;">
       <div class="book-post">
        <div align="center">
         <a href="https://www.aliyun.com/minisite/goods?userCode=lc4iupk4" target="_blank">
          阿里云2C2G3M 99元/年，老用户也可以哦
         </a>
         <hr/>
        </div>
        <p align="center" id="tip">
        </p>
        <p class="title">
         08 答疑现场：Spring Core 篇思考题合集
        </p>
        <div>
         <p>
          你好，我是傅健。
         </p>
         <p>
          如果你看到这篇文章，那么我真的非常开心，这说明第一章节的内容你都跟下来了，并且对于课后的思考题也有研究，在这我要手动给你点个赞。繁忙的工作中，还能为自己持续充电，保持终身学习的心态，我想我们一定是同路人。
         </p>
         <p>
          那么到今天为止，我们已经学习了 17 个案例，解决的问题也不算少了，不知道你的感受如何？收获如何呢？
         </p>
         <p>
          我还记得
          <a href="https://time.geekbang.org/column/article/364661" target="_blank">
           开篇词
          </a>
          的留言区中有位很有趣的同学，他说：“作为一线 bug 制造者，希望能少写点 bug。” 感同身受，和 Spring 斗智斗勇的这些年，我也经常为一些问题而抓狂过，因不能及时解决而焦虑过，但最终还是觉得蛮有趣的，这个专栏也算是沉淀之作，希望能给你带来一些实际的帮助。
         </p>
         <p>
          最初，我其实是想每节课都和你交流下上节课的思考题，但又担心大家的学习进度不一样，所以就有了这次的集中答疑，我把我的答案给到大家，你也可以对照着去看一看，也许有更好的方法，欢迎你来贡献“选项”，我们一起交流。希望大家都能在问题的解决中获得一些正向反馈，完成学习闭环。
         </p>
         <h2 id="第1课-https-time-geekbang-org-column-article-364761">
          <strong>
           <a href="https://time.geekbang.org/column/article/364761" target="_blank">
            第1课
           </a>
          </strong>
         </h2>
         <p>
          在案例 2 中，显示定义构造器，这会发生根据构造器参数寻找对应 Bean 的行为。这里请你思考一个问题，假设寻找不到对应的 Bean，一定会如案例 2 那样直接报错么？
         </p>
         <p>
          实际上，答案是否定的。这里我们不妨修改下案例 2 的代码，修改后如下：
         </p>
         <pre><code>@Service
public class ServiceImpl {
    private List&lt;String&gt; serviceNames;
    public ServiceImpl(List&lt;String&gt; serviceNames){
        this.serviceNames = serviceNames;
        System.out.println(this.serviceNames);
    }
}
</code></pre>
         <p>
          参考上述代码，我们的构造器参数由普通的String改成了一个List ，最终运行程序会发现这并不会报错，而是输出 []。
         </p>
         <p>
          要了解这个现象，我们可以直接定位构建构造器调用参数的代码所在地（即 ConstructorResolver#resolveAutowiredArgument）：
         </p>
         <pre><code>@Nullable
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
      @Nullable Set&lt;String&gt; autowiredBeanNames, TypeConverter typeConverter, boolean fallback) {

   //省略非关键代码
   try {
      //根据构造器参数寻找 bean
      return this.beanFactory.resolveDependency(
            new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
   }
   catch (NoUniqueBeanDefinitionException ex) {
      throw ex;
   }
   catch (NoSuchBeanDefinitionException ex) {
      //找不到 “bean” 进行fallback
      if (fallback) {
         // Single constructor or factory method -&gt; let's return an empty array/collection
         // for e.g. a vararg or a non-null List/Set/Map parameter.
         if (paramType.isArray()) {
            return Array.newInstance(paramType.getComponentType(), 0);
         }
         else if (CollectionFactory.isApproximableCollectionType(paramType)) {
            return CollectionFactory.createCollection(paramType, 0);
         }
         else if (CollectionFactory.isApproximableMapType(paramType)) {
            return CollectionFactory.createMap(paramType, 0);
         }
      }
      throw ex;
   }
}
</code></pre>
         <p>
          当构建集合类型的参数实例寻找不到合适的 Bean 时，并不是不管不顾地直接报错，而是会尝试进行fallback。对于本案例而言，会使用下面的语句来创建一个空的集合作为构造器参数传递进去：
         </p>
         <blockquote>
          <p>
           CollectionFactory.createCollection(paramType, 0);
          </p>
         </blockquote>
         <p>
          上述代码最终调用代码如下：
         </p>
         <blockquote>
          <p>
           return new ArrayList&lt;&gt;(capacity);
          </p>
         </blockquote>
         <p>
          所以很明显，最终修改后的案例并不会报错，而是把 serviceNames 设置为一个空的 List。从这一点也可知，
          <strong>
           自动装配远比想象的要复杂
          </strong>
          。
         </p>
         <h2 id="第2课-https-time-geekbang-org-column-article-366170">
          <strong>
           <a href="https://time.geekbang.org/column/article/366170" target="_blank">
            第2课
           </a>
          </strong>
         </h2>
         <p>
          我们知道了通过@Qualifier可以引用想匹配的Bean，也可以直接命名属性的名称为Bean的名称来引用，这两种方式如下：
         </p>
         <pre><code>//方式1：属性命名为要装配的bean名称
@Autowired
DataService oracleDataService;

//方式2：使用@Qualifier直接引用
@Autowired
@Qualifier("oracleDataService")
DataService dataService;
</code></pre>
         <p>
          那么对于案例3的内部类引用，你觉得可以使用第1种方式做到么？例如使用如下代码：
         </p>
         <blockquote>
          <p>
           @Autowired-
DataService studentController.InnerClassDataService;
          </p>
         </blockquote>
         <p>
          实际上，如果你动动手或者我们稍微敏锐点就会发现，代码本身就不能编译，因为中间含有“.”。那么还有办法能通过这种方式引用到内部类么？
         </p>
         <p>
          查看决策谁优先的源码，最终使用属性名来匹配的执行情况可参考DefaultListableBeanFactory#matchesBeanName方法的调试视图：
         </p>
         <p>
          <img alt="" src="assets/5ebdca5725f745d59e7b18fd160d3c19.jpg"/>
         </p>
         <p>
          我们可以看到实现的关键其实是下面这行语句：
         </p>
         <blockquote>
          <p>
           candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName))
          </p>
         </blockquote>
         <p>
          很明显，我们的Bean没有被赋予别名，而鉴于属性名不可能含有“.”，所以它不可能匹配上带“.”的Bean名（即studentController.InnerClassDataService）。
         </p>
         <p>
          综上，如果一个内部类，没有显式指定名称或者别名，试图使用属性名和Bean名称一致来引用到对应的Bean是行不通的。
         </p>
         <h2 id="第3课-https-time-geekbang-org-column-article-366930">
          <strong>
           <a href="https://time.geekbang.org/column/article/366930" target="_blank">
            第3课
           </a>
          </strong>
         </h2>
         <p>
          在案例2中，我们初次运行程序获取的结果如下：
         </p>
         <blockquote>
          <p>
           [Student(id=1, name=xie), Student(id=2, name=fang)]
          </p>
         </blockquote>
         <p>
          那么如何做到让学生2优先输出呢？
         </p>
         <p>
          实际上，在案例2中，我们收集的目标类型是List，而List是可排序的，那么到底是如何排序的？在案例2的解析中，我们给出了DefaultListableBeanFactory#resolveMultipleBeans方法的代码，不过省略了一些非关键的代码，这其中就包括了排序工作，代码如下：
         </p>
         <pre><code>if (result instanceof List) {
   Comparator&lt;Object&gt; comparator = adaptDependencyComparator(matchingBeans);
   if (comparator != null) {
      ((List&lt;?&gt;) result).sort(comparator);
   }
}
</code></pre>
         <p>
          而针对本案例最终排序执行的是OrderComparator#doCompare方法，关键代码如下：
         </p>
         <pre><code>private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
   boolean p1 = (o1 instanceof PriorityOrdered);
   boolean p2 = (o2 instanceof PriorityOrdered);
   if (p1 &amp;&amp; !p2) {
      return -1;
   }
   else if (p2 &amp;&amp; !p1) {
      return 1;
   }

   int i1 = getOrder(o1, sourceProvider);
   int i2 = getOrder(o2, sourceProvider);
   return Integer.compare(i1, i2);
}
</code></pre>
         <p>
          其中getOrder的执行，获取到的order值（相当于优先级）是通过AnnotationAwareOrderComparator#findOrder来获取的：
         </p>
         <pre><code>protected Integer findOrder(Object obj) {
   Integer order = super.findOrder(obj);
   if (order != null) {
      return order;
   }
   return findOrderFromAnnotation(obj);
}
</code></pre>
         <p>
          不难看出，获取order值包含了2种方式：
         </p>
         <ol>
          <li>
           从@Order获取值，参考AnnotationAwareOrderComparator#findOrderFromAnnotation：
          </li>
         </ol>
         <pre><code>@Nullable
private Integer findOrderFromAnnotation(Object obj) {
   AnnotatedElement element = (obj instanceof AnnotatedElement ? (AnnotatedElement) obj : obj.getClass());
   MergedAnnotations annotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY);
   Integer order = OrderUtils.getOrderFromAnnotations(element, annotations);
   if (order == null &amp;&amp; obj instanceof DecoratingProxy) {
      return findOrderFromAnnotation(((DecoratingProxy) obj).getDecoratedClass());
   }
   return order;
}
</code></pre>
         <ol>
          <li>
           从Ordered 接口实现方法获取值，参考OrderComparator#findOrder：
          </li>
         </ol>
         <pre><code>protected Integer findOrder(Object obj) {
   return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}
</code></pre>
         <p>
          通过上面的分析，如果我们不能改变类继承关系（例如让Student实现Ordered接口），则可以通过使用@Order来调整顺序，具体修改代码如下：
         </p>
         <pre><code>@Bean
@Order(2)
public Student student1(){
    return createStudent(1, "xie");
}

@Bean
@Order(1)
public Student student2(){
    return createStudent(2, "fang");
}
</code></pre>
         <p>
          现在，我们就可以把原先的Bean输出顺序颠倒过来了，示例如下：
         </p>
         <blockquote>
          <p>
           Student(id=2, name=fang)],[Student(id=1, name=xie)
          </p>
         </blockquote>
         <h2 id="第4课-https-time-geekbang-org-column-article-367876">
          <strong>
           <a href="https://time.geekbang.org/column/article/367876" target="_blank">
            第4课
           </a>
          </strong>
         </h2>
         <p>
          案例 2 中的类 LightService，当我们不在 Configuration 注解类中使用 Bean 方法将其注入 Spring 容器，而是坚持使用 @Service 将其自动注入到容器，同时实现 Closeable 接口，代码如下：
         </p>
         <pre><code>import org.springframework.stereotype.Component;
import java.io.Closeable;
@Service
public class LightService implements Closeable {
    public void close() {
        System.out.println("turn off all lights);
    }
    //省略非关键代码
}
</code></pre>
         <p>
          接口方法 close() 也会在 Spring 容器被销毁的时候自动执行么？
         </p>
         <p>
          答案是肯定的，通过案例 2 的分析，你可以知道，当 LightService 是一个实现了 Closable 接口的单例 Bean 时，会有一个 DisposableBeanAdapter 被添加进去。
         </p>
         <p>
          而具体到执行哪一种方法？shutdown()？close()? 在代码中你能够找到答案，在 DisposableBeanAdapter 类的 inferDestroyMethodIfNecessary 中，我们可以看到有两种情况会获取到当前 Bean 类中的 close()。
         </p>
         <p>
          第一种情况，就是我们这节课提到的当使用@Bean且使用默认的 destroyMethod 属性（INFER_METHOD）；第二种情况，是判断当前类是否实现了 AutoCloseable 接口，如果实现了，那么一定会获取此类的 close()。
         </p>
         <pre><code>private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
   String destroyMethodName = beanDefinition.getDestroyMethodName();
   if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName == null &amp;&amp; bean instanceof AutoCloseable)) {
      if (!(bean instanceof DisposableBean)) {
         try {
            return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
         }
         catch (NoSuchMethodException ex) {
            try {
               return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
            }
            catch (NoSuchMethodException ex2) {
               // no candidate destroy method found
            }
         }
      }
      return null;
   }
   return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
</code></pre>
         <p>
          到这，相信你应该可以结合 Closable 接口和@Service（或其他@Component）让关闭方法得到执行了。
         </p>
         <h2 id="第5课-https-time-geekbang-org-column-article-369251">
          <strong>
           <a href="https://time.geekbang.org/column/article/369251" target="_blank">
            第5课
           </a>
          </strong>
         </h2>
         <p>
          案例2中，我们提到了通过反射来实例化类的三种方式：
         </p>
         <ul>
          <li>
           java.lang.Class.newInsance()
          </li>
          <li>
           java.lang.reflect.Constructor.newInstance()
          </li>
          <li>
           sun.reflect.ReflectionFactory.newConstructorForSerialization().newInstance()
          </li>
         </ul>
         <p>
          其中第三种方式不会初始化类属性，你能够写一个例子来证明这一点吗？
         </p>
         <p>
          能证明的例子，代码示例如下：
         </p>
         <pre><code>import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;

public class TestNewInstanceStyle {

    public static class TestObject{
        public String name = "fujian";
    }

    public static void main(String[] args) throws Exception {
        //ReflectionFactory.newConstructorForSerialization()方式
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        Constructor constructor = reflectionFactory.newConstructorForSerialization(TestObject.class, Object.class.getDeclaredConstructor());
        constructor.setAccessible(true);
        TestObject testObject1 = (TestObject) constructor.newInstance();
        System.out.println(testObject1.name);
        //普通方式
        TestObject testObject2 = new TestObject();
        System.out.println(testObject2.name);
    }

}
</code></pre>
         <p>
          运行结果如下：
         </p>
         <blockquote>
          <p>
           null-
fujian
          </p>
         </blockquote>
         <h2 id="第6课-https-time-geekbang-org-column-article-369989">
          <strong>
           <a href="https://time.geekbang.org/column/article/369989" target="_blank">
            第6课
           </a>
          </strong>
         </h2>
         <p>
          实际上，审阅这节课两个案例的修正方案，你会发现它们虽然改动很小，但是都还不够优美。那么有没有稍微优美点的替代方案呢？如果有，你知道背后的原理及关键源码吗？顺便你也可以想想，我为什么没有用更优美的方案呢？
         </p>
         <p>
          我们可以将“未达到执行顺序预期”的增强方法移动到一个独立的切面类，而不同的切面类可以使用 @Order 进行修饰。@Order 的 value 值越低，则执行优先级越高。以案例 2 为例，可以修改如下：
         </p>
         <pre><code>@Aspect
@Service
@Order(1)
public class AopConfig1 {
    @Before("execution(* com.spring.puzzle.class6.example2.ElectricService.charge()) ")
    public void validateAuthority(JoinPoint pjp) throws Throwable {
        throw new RuntimeException("authority check failed");
    }
}


@Aspect
@Service
@Order(2)
public class AopConfig2 {

    @Before("execution(* com.spring.puzzle.class6.example2.ElectricService.charge())")
    public void logBeforeMethod(JoinPoint pjp) throws Throwable {
        System.out.println("step into -&gt;"+pjp.getSignature());
    }

}
</code></pre>
         <p>
          上述修改的核心就是将原来的 AOP 配置，切成两个类进行，并分别使用@Order标记下优先级。这样修改后，当授权失败了，则不会打印“step into -&gt;”相关日志。
         </p>
         <p>
          为什么这样是可行的呢？这还得回溯到案例1，当时我们提出这样一个结论：AbstractAdvisorAutoProxyCreator 执行 findEligibleAdvisors（代码如下）寻找匹配的 Advisors 时，最终返回的 Advisors 顺序是由两点来决定的：candidateAdvisors 的顺序和 sortAdvisors 执行的排序。
         </p>
         <pre><code>protected List&lt;Advisor&gt; findEligibleAdvisors(Class&lt;?&gt; beanClass, String beanName) {
   List&lt;Advisor&gt; candidateAdvisors = findCandidateAdvisors();
   List&lt;Advisor&gt; eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}
</code></pre>
         <p>
          当时影响我们案例出错的关键点都是在 candidateAdvisors 的顺序上，所以我们重点介绍了它。而对于 sortAdvisors 执行的排序并没有多少涉及，这里我可以再重点介绍下。
         </p>
         <p>
          在实现上，sortAdvisors 的执行最终调用的是比较器 AnnotationAwareOrderComparator 类的 compare()，它调用了 getOrder() 的返回值作为排序依据：
         </p>
         <pre><code>public int compare(@Nullable Object o1, @Nullable Object o2) {
   return doCompare(o1, o2, null);
}

private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
   boolean p1 = (o1 instanceof PriorityOrdered);
   boolean p2 = (o2 instanceof PriorityOrdered);
   if (p1 &amp;&amp; !p2) {
      return -1;
   }
   else if (p2 &amp;&amp; !p1) {
      return 1;
   }

   int i1 = getOrder(o1, sourceProvider);
   int i2 = getOrder(o2, sourceProvider);
   return Integer.compare(i1, i2);
}
</code></pre>
         <p>
          继续跟踪 getOrder() 的执行细节，我们会发现对于我们的案例，这个方法会找出配置切面的 Bean 的 Order值。这里可以参考 BeanFactoryAspectInstanceFactory#getOrder 的调试视图验证这个结论：
         </p>
         <p>
          <img alt="" src="assets/57691fdf7c94454a9da898113608d70d.jpg"/>
         </p>
         <p>
          上述截图中，aopConfig2 即是我们配置切面的 Bean 的名称。这里再顺带提供出调用栈的截图，以便你做进一步研究：
         </p>
         <p>
          <img alt="" src="assets/5da30f9691264c2eb3e71f15b7d04da6.jpg"/>
         </p>
         <p>
          现在我们就知道了，将不同的增强方法放置到不同的切面配置类中，使用不同的 Order 值来修饰是可以影响顺序的。相反，如果都是在一个配置类中，自然不会影响顺序，所以这也是当初我的方案中没有重点介绍 sortAdvisors 方法的原因，毕竟当时我们给出的案例都只有一个 AOP 配置类。
         </p>
         <h2 id="第7课-https-time-geekbang-org-column-article-370741">
          <strong>
           <a href="https://time.geekbang.org/column/article/370741" target="_blank">
            第7课
           </a>
          </strong>
         </h2>
         <p>
          在案例 3 中，我们提到默认的事件执行是在同一个线程中执行的，即事件发布者使用的线程。参考如下日志佐证这个结论：
         </p>
         <blockquote>
          <p>
           2021-03-09 09:10:33.052 INFO 18104 — [nio-8080-exec-1] c.s.p.listener.HelloWorldController : start to publish event-
2021-03-09 09:10:33.055 INFO 18104 — [nio-8080-exec-1] c.s.p.l.example3.MyFirstEventListener : com.spring.puzzle.class7.example3.MyFirstEventListener@18faf0 received: com.spring.puzzle.class7.example3.MyEvent[source=df42b08f-8ee2-44df-a957-d8464ff50c88]
          </p>
         </blockquote>
         <p>
          通过日志可以看出，事件的发布和执行使用的都是nio-8080-exec-1线程，但是在事件比较多时，我们往往希望事件执行得更快些，或者希望事件的执行可以异步化以不影响主线程。此时应该如何做呢？
         </p>
         <p>
          针对上述问题中的需求，我们只需要对于事件的执行引入线程池即可。我们先来看下 Spring 对这点的支持。实际上，在案例 3 的解析中，我们已贴出了以下代码片段（位于 SimpleApplicationEventMulticaster#multicastEvent 方法中）：
         </p>
         <pre><code> //省略其他非关键代码
 //获取 executor 
 Executor executor = getTaskExecutor();
   for (ApplicationListener&lt;?&gt; listener : getApplicationListeners(event, type)) {
      //如果存在 executor，则提交到 executor 中去执行
      if (executor != null) {
         executor.execute(() -&gt; invokeListener(listener, event));
      }
 //省略其他非关键代码
</code></pre>
         <p>
          对于事件的处理，可以绑定一个 Executor 去执行，那么如何绑定？其实与这节课讲过的绑定 ErrorHandler 的方法是类似的。绑定代码示例如下：
         </p>
         <pre><code>//注意下面的语句只能执行一次，以避免重复创建线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//省略非关键代码
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = applicationContext.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, SimpleApplicationEventMulticaster.class);
simpleApplicationEventMulticaster.setTaskExecutor(newCachedThreadPool );
</code></pre>
         <p>
          取出SimpleApplicationEventMulticaster，然后直接调用相关 set() 设置线程池就可以了。按这种方式修改后的程序，事件处理的日志如下：
         </p>
         <blockquote>
          <p>
           2021-03-09 09:25:09.917 INFO 16548 — [nio-8080-exec-1] c.s.p.c.HelloWorldController : start to publish event-
2021-03-09 09:25:09.920 INFO 16548 — [pool-1-thread-3] c.s.p.l.example3.MyFirstEventListener : com.spring.puzzle.class7.example3.MyFirstEventListener@511056 received: com.spring.puzzle.class7.example3.MyEvent[source=cbb97bcc-b834-485c-980e-2e20de56c7e0]
          </p>
         </blockquote>
         <p>
          可以看出，事件的发布和处理分属不同的线程了，分别为 nio-8080-exec-1 和 pool-1-thread-3，满足了我们的需求。
         </p>
         <p>
          以上就是这次答疑的全部内容，我们下一章节再见！
         </p>
        </div>
       </div>
       <div>
        <div id="prePage" style="float: left">
        </div>
        <div id="nextPage" style="float: right">
        </div>
       </div>
      </div>
     </div>
    </div>
    <div class="copyright">
     <hr/>
     <p>
      © 2019 - 2023
      <a href="/cdn-cgi/l/email-protection#bfd3d3d3868b8e8e8f88ffd8d2ded6d391dcd0d2" target="_blank">
       Liangliang Lee
      </a>
      .
                    Powered by
      <a href="https://github.com/gin-gonic/gin" target="_blank">
       gin
      </a>
      and
      <a href="https://github.com/kaiiiz/hexo-theme-book" target="_blank">
       hexo-theme-book
      </a>
      .
     </p>
    </div>
   </div>
   <a class="off-canvas-overlay" onclick="hide_canvas()">
   </a>
  </div>
  <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js">
  </script>
  <script>
   (function(){var js = "window['__CF$cv$params']={r:'824e6188699504d0',t:'MTY5OTc4Nzk3Ny4wOTQwMDA='};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js',document.getElementsByTagName('head')[0].appendChild(_cpo);";var _0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position = 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility = 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi = _0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj = _0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if (document.readyState !== 'loading') {handler();} else if (window.addEventListener) {document.addEventListener('DOMContentLoaded', handler);} else {var prev = document.onreadystatechange || function () {};document.onreadystatechange = function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange = prev;handler();}};}})();
  </script>
  <script crossorigin="anonymous" data-cf-beacon='{"rayId":"824e6188699504d0","version":"2023.10.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","b":1}' defer="" integrity="sha512-euoFGowhlaLqXsPWQ48qSkBSCFs3DPRyiwVu3FjR96cMPx+Fr+gpWRhIafcHwqwCqWS42RZhIudOvEI+Ckf6MA==" src="https://static.cloudflareinsights.com/beacon.min.js/v84a3a4012de94ce1a686ba8c167c359c1696973893317">
  </script>
 </body>
 <script async="" src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756">
 </script>
 <script src="/static/index.js">
 </script>
</html>
